Image of a blue and purple rose on a light pink background. A section of the rose is magnified to reveal the RGBA values of a specific pixel.
Strategy

Nailing the Perfect Contrast Between Light Text and a Backgr…


Have you ever come across a site where light text is sitting on a light background image? If you have, you’ll know how difficult that is to read. A popular way to avoid that is to use a transparent overlay. But this leads to an important question: Just how transparent should that overlay be? It’s not like we’re always dealing with the same font sizes, weights, and colors, and, of course, different images will result in different contrasts.

Trying to stamp out poor text contrast on background images is a lot like playing Whac-a-Mole. Instead of guessing, we can solve this problem with HTML <canvas> and a little bit of math.

Like this:

We could say “Problem solved!” and simply end this article here. But where’s the fun in that? What I want to show you is how this tool works so you have a new way to handle this all-too-common problem.

Here’s the plan

First, let’s get specific about our goals. We’ve said we want readable text on top of a background image, but what does “readable” even mean? For our purposes, we’ll use the WCAG definition of AA-level readability, which says text and background colors need enough contrast between them such that that one color is 4.5 times lighter than the other.

Let’s pick a text color, a background image, and an overlay color as a starting point. Given those inputs, we want to find the overlay opacity level that makes the text readable without hiding the image so much that it, too, is difficult to see. To complicate things a bit, we’ll use an image with both dark and light space and make sure the overlay takes that into account.

Our final result will be a value we can apply to the CSS opacity property of the overlay that gives us the right amount of transparency that makes the text 4.5 times lighter than the background.

Optimal overlay opacity: 0.521

To find the optimal overlay opacity we’ll go through four steps:

  1. We’ll put the image in an HTML <canvas>, which will let us read the colors of each pixel in the image.
  2. We’ll find the pixel in the image that has the least contrast with the text.
  3. Next, we’ll prepare a color-mixing formula we can use to test different opacity levels on top of that pixel’s color.
  4. Finally, we’ll adjust the opacity of our overlay until the text contrast hits the readability goal. And these won’t just be random guesses — we’ll use binary search techniques to make this process quick.

Let’s get started!

Step 1: Read image colors from the canvas

Canvas lets us “read” the colors contained in an image. To do that, we need to “draw” the image onto a <canvas> element and then use the canvas context (ctx) getImageData() method to produce a list of the image’s colors.

function getImagePixelColorsUsingCanvas(image, canvas) {
  // The canvas's context (often abbreviated as ctx) is an object
  // that contains a bunch of functions to control your canvas
  const ctx = canvas.getContext('2d');


  // The width can be anything, so I picked 500 because it's large
  // enough to catch details but small enough to keep the
  // calculations quick.
  canvas.width = 500;


  // Make sure the canvas matches proportions of our image
  canvas.height = (image.height / image.width) * canvas.width;


  // Grab the image and canvas measurements so we can use them in the next step
  const sourceImageCoordinates = [0, 0, image.width, image.height];
  const destinationCanvasCoordinates = [0, 0, canvas.width, canvas.height];


  // Canvas's drawImage() works by mapping our image's measurements onto
  // the canvas where we want to draw it
  ctx.drawImage(
    image,
    ...sourceImageCoordinates,
    ...destinationCanvasCoordinates
  );


  // Remember that getImageData only works for same-origin or 
  // cross-origin-enabled images.
  // https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
  const imagePixelColors = ctx.getImageData(...destinationCanvasCoordinates);
  return imagePixelColors;
}

The getImageData() method gives us a list of numbers representing the colors in each pixel. Each pixel is represented by four numbers: red, green, blue, and opacity (also called “alpha”). Knowing this, we can loop through the list of pixels and find whatever info we need. This will be useful in the next step.

Image of a blue and purple rose on a light pink background. A section of the rose is magnified to reveal the RGBA values of a specific pixel.

Step 2: Find the pixel with the least contrast

Before we do this, we need to know how to calculate contrast. We’ll write a function called getContrast() that takes in two colors and spits out a number representing the level of contrast between the two. The higher the number, the better the contrast for legibility.

When I started researching colors for this project, I was expecting to find a simple formula. It turned out there were multiple steps.

To calculate the contrast between two colors, we need to know their luminance levels, which is essentially the brightness (Stacie Arellano does a deep dive on luminance that’s worth checking out.)

Thanks to the W3C, we know the formula for calculating contrast using luminance:

const contrast = (lighterColorLuminance + 0.05) / (darkerColorLuminance + 0.05);

Getting the luminance of a color means we have to convert the color from the regular 8-bit RGB value used on the web (where each color is 0-255) to what’s called linear RGB. The reason we need to do this is that brightness doesn’t increase evenly as colors change. We need to convert our colors into a format where the brightness does vary evenly with color changes. That allows us to properly calculate luminance. Again, the W3C is a help here:

const luminance = (0.2126 * getLinearRGB(r) + 0.7152 * getLinearRGB(g) + 0.0722 * getLinearRGB(b));

But wait, there’s more! In order to convert 8-bit RGB (0 to 255) to linear RGB, we need to go through what’s called standard RGB (also called sRGB), which is on a scale from 0 to 1.

So the process goes: 

8-bit RGB → standard RGB  → linear RGB → luminance

And once we have the luminance of both colors we want to compare, we can plug in the luminance values to get the contrast between their respective colors.

// getContrast is the only function we need to interact with directly.
// The rest of the functions are intermediate helper steps.
function getContrast(color1, color2) {
  const color1_luminance = getLuminance(color1);
  const color2_luminance = getLuminance(color2);
  const lighterColorLuminance = Math.max(color1_luminance, color2_luminance);
  const darkerColorLuminance = Math.min(color1_luminance, color2_luminance);
  const contrast = (lighterColorLuminance + 0.05) / (darkerColorLuminance + 0.05);
  return contrast;
}


function getLuminance({r,g,b}) {
  return (0.2126 * getLinearRGB(r) + 0.7152 * getLinearRGB(g) + 0.0722 * getLinearRGB(b));
}
function getLinearRGB(primaryColor_8bit) {
  // First convert from 8-bit rbg (0-255) to standard RGB (0-1)
  const primaryColor_sRGB = convert_8bit_RGB_to_standard_RGB(primaryColor_8bit);


  // Then convert from sRGB to linear RGB so we can use it to calculate luminance
  const primaryColor_RGB_linear = convert_standard_RGB_to_linear_RGB(primaryColor_sRGB);
  return primaryColor_RGB_linear;
}
function convert_8bit_RGB_to_standard_RGB(primaryColor_8bit) {
  return primaryColor_8bit / 255;
}
function convert_standard_RGB_to_linear_RGB(primaryColor_sRGB) {
  const primaryColor_linear = primaryColor_sRGB < 0.03928 ?
    primaryColor_sRGB/12.92 :
    Math.pow((primaryColor_sRGB + 0.055) / 1.055, 2.4);
  return primaryColor_linear;
}

Now that we can calculate contrast, we’ll need to look at our image from the previous step and loop through each pixel, comparing the contrast between that pixel’s color and the foreground text color. As we loop through the image’s pixels, we’ll keep track of the worst (lowest) contrast so far, and when we reach the end of the loop, we’ll know the worst-contrast color in the image.

function getWorstContrastColorInImage(textColor, imagePixelColors) {
  let worstContrastColorInImage;
  let worstContrast = Infinity; // This guarantees we won't start too low
  for (let i = 0; i < imagePixelColors.data.length; i += 4) {
    let pixelColor = {
      r: imagePixelColors.data[i],
      g: imagePixelColors.data[i + 1],
      b: imagePixelColors.data[i + 2],
    };
    let contrast = getContrast(textColor, pixelColor);
    if(contrast < worstContrast) {
      worstContrast = contrast;
      worstContrastColorInImage = pixelColor;
    }
  }
  return worstContrastColorInImage;
}

Step 3: Prepare a color-mixing formula to test overlay opacity levels

Now that we know the worst-contrast color in our image, the next step is to establish how transparent the overlay should be and see how that changes the contrast with the text.

When I first implemented this, I used a separate canvas to mix colors and read the results. However, thanks to Ana Tudor’s article about transparency, I now know there’s a convenient formula to calculate the resulting color from mixing a base color with a transparent overlay.

For each color channel (red, green, and blue), we’d apply this formula to get the mixed color:

mixedColor = baseColor + (overlayColor - baseColor) * overlayOpacity

So, in code, that would look like this:

function mixColors(baseColor, overlayColor, overlayOpacity) {
  const mixedColor = {
    r: baseColor.r + (overlayColor.r - baseColor.r) * overlayOpacity,
    g: baseColor.g + (overlayColor.g - baseColor.g) * overlayOpacity,
    b: baseColor.b + (overlayColor.b - baseColor.b) * overlayOpacity,
  }
  return mixedColor;
}

Now that we’re able to mix colors, we can test the contrast when the overlay opacity value is applied.

function getTextContrastWithImagePlusOverlay({textColor, overlayColor, imagePixelColor, overlayOpacity}) {
  const colorOfImagePixelPlusOverlay = mixColors(imagePixelColor, overlayColor, overlayOpacity);
  const contrast = getContrast(this.state.textColor, colorOfImagePixelPlusOverlay);
  return contrast;
}

With that, we have all the tools we need to find the optimal overlay opacity!

Step 4: Find the overlay opacity that hits our contrast goal

We can test an overlay’s opacity and see how that affects the contrast between the text and image. We’re going to try a bunch of different opacity levels until we find the contrast that hits our mark where the text is 4.5 times lighter than the background. That may sound crazy, but don’t worry; we’re not going to guess randomly. We’ll use a binary search, which is a process that lets us quickly narrow down the possible set of answers until we get a precise result.

Here’s how a binary search works:

  • Guess in the middle.
  • If the guess is too high, we eliminate the top half of the answers. Too low? We eliminate the bottom half instead.
  • Guess in the middle of that new range.
  • Repeat this process until we get a value.

I just so happen to have a tool to show how this works:

In this case, we’re trying to guess an opacity value that’s between 0 and 1. So, we’ll guess in the middle, test whether the resulting contrast is too high or too low, eliminate half the options, and guess again. If we limit the binary search to eight guesses, we’ll get a precise answer in a snap.

Before we start searching, we’ll need a way to check if an overlay is even necessary in the first place. There’s no point optimizing an overlay we don’t even need!

function isOverlayNecessary(textColor, worstContrastColorInImage, desiredContrast) {
  const contrastWithoutOverlay = getContrast(textColor, worstContrastColorInImage);
  return contrastWithoutOverlay < desiredContrast;
}

Now we can use our binary search to look for the optimal overlay opacity:

function findOptimalOverlayOpacity(textColor, overlayColor, worstContrastColorInImage, desiredContrast) {
  // If the contrast is already fine, we don't need the overlay,
  // so we can skip the rest.
  const isOverlayNecessary = isOverlayNecessary(textColor, worstContrastColorInImage, desiredContrast);
  if (!isOverlayNecessary) {
    return 0;
  }


  const opacityGuessRange = {
    lowerBound: 0,
    midpoint: 0.5,
    upperBound: 1,
  };
  let numberOfGuesses = 0;
  const maxGuesses = 8;


  // If there's no solution, the opacity guesses will approach 1,
  // so we can hold onto this as an upper limit to check for the no-solution case.
  const opacityLimit = 0.99;


  // This loop repeatedly narrows down our guesses until we get a result
  while (numberOfGuesses < maxGuesses) {
    numberOfGuesses++;


    const currentGuess = opacityGuessRange.midpoint;
    const contrastOfGuess = getTextContrastWithImagePlusOverlay({
      textColor,
      overlayColor,
      imagePixelColor: worstContrastColorInImage,
      overlayOpacity: currentGuess,
    });


    const isGuessTooLow = contrastOfGuess < desiredContrast;
    const isGuessTooHigh = contrastOfGuess > desiredContrast;
    if (isGuessTooLow) {
      opacityGuessRange.lowerBound = currentGuess;
    }
    else if (isGuessTooHigh) {
      opacityGuessRange.upperBound = currentGuess;
    }


    const newMidpoint = ((opacityGuessRange.upperBound - opacityGuessRange.lowerBound) / 2) + opacityGuessRange.lowerBound;
    opacityGuessRange.midpoint = newMidpoint;
  }


  const optimalOpacity = opacityGuessRange.midpoint;
  const hasNoSolution = optimalOpacity > opacityLimit;


  if (hasNoSolution) {
    console.log('No solution'); // Handle the no-solution case however you'd like
    return opacityLimit;
  }
  return optimalOpacity;
}

With our experiment complete, we now know exactly how transparent our overlay needs to be to keep our text readable without hiding the background image too much.

We did it!

Improvements and limitations

The methods we’ve covered only work if the text color and the overlay color have enough contrast to begin with. For example, if you were to choose a text color that’s the same as your overlay, there won’t be an optimal solution unless the image doesn’t need an overlay at all.

In addition, even if the contrast is mathematically acceptable, that doesn’t always guarantee it’ll look great. This is especially true for dark text with a light overlay and a busy background image. Various parts of the image may distract from the text, making it difficult to read even when the contrast is numerically fine. That’s why the popular recommendation is to use light text on a dark background.

We also haven’t taken where the pixels are located into account or how many there are of each color. One drawback of that is that a pixel in the corner could possibly exert too much influence on the result. The benefit, however, is that we don’t have to worry about how the image’s colors are distributed or where the text is because, as long as we’ve handled where the least amount of contrast is, we’re safe everywhere else.

I learned a few things along the way

There are some things I walked away with after this experiment, and I’d like to share them with you:

  • Getting specific about a goal really helps! We started with a vague goal of wanting readable text on an image, and we ended up with a specific contrast level we could strive for.
  • It’s so important to be clear about the terms. For example, standard RGB wasn’t what I expected. I learned that what I thought of as “regular” RGB (0 to 255) is formally called 8-bit RGB. Also, I thought the “L” in the equations I researched meant “lightness,” but it actually means “luminance,” which is not to be confused with “luminosity.” Clearing up terms helps how we code as well as how we discuss the end result.
  • Complex doesn’t mean unsolvable. Problems that sound hard can be broken into smaller, more manageable pieces.
  • When you walk the path, you spot the shortcuts. For the common case of white text on a black transparent overlay, you’ll never need an opacity over 0.54 to achieve WCAG AA-level readability.

In summary…

You now have a way to make your text readable on a background image without sacrificing too much of the image. If you’ve gotten this far, I hope I’ve been able to give you a general idea of how it all works.

I originally started this project because I saw (and made) too many website banners where the text was tough to read against a background image or the background image was overly obscured by the overlay. I wanted to do something about it, and I wanted to give others a way to do the same. I wrote this article in hopes that you’d come away with a better understanding of readability on the web. I hope you’ve learned some neat canvas tricks too.

If you’ve done something interesting with readability or canvas, I’d love to hear about it in the comments!



Source link

DejaVu Linux test
Strategy

300 considered harmful (and a fontconfig workaround) — Tomáš…


published (revision history)

Many web pages these days set font-weight: 300 in their stylesheet. With
DejaVu Sans as my preferred font, this
results in very thin and light text that is hard to read, because for some
reason the “DejaVu Sans ExtraLight” variant (weight 200) is being used for
weights < 360 (in Chrome; in Firefox up to 399). Let’s investigate why this
happens and what can be done about it.

Table of Contents

The Problem

Here’s what a test page looks like on my
laptop (14” 1920×1080):

DejaVu Linux test

DejaVu Sans at different font-weights

For comparison, and possibly also as a clue as to why web designers use
font-weight: 300, here’s a table of various font-weights of DejaVu Sans on
my system and the default sans-serif font on MacOS Catalina and Android
(unfortunately I don’t have any HiDPI laptop or low-DPI smartphone, so the
comparison might be imprecise/unfair):

Boldness comparison (scaled to equal
height)

MacOS font smoothing, CSS

In MacOS, font-weight: normal looks almost bold, so web designers who use
MacOS/Safari might use font-weight: 300 to compensate for this, ruining it
for everybody else
. 🙁

Well, actually not everybody, as some desktop users (e.g. a Fedora Live DVD)
won’t have an extra-light variant of sans serif, so the normal (regular, or
book) variant will be used for all weights. But Android users and desktop
users with DejaVu (used to be default on most Linux distributions, not sure
what’s the current status) and possibly also Windows users are affected.

Nikita Prokopov suggested that disabling font smoothing in MacOS reduces the
boldness
, and my
experiments confirm that. Furthermore, subpixel smoothing
(antialiasing) comes somewhere in the middle between the
default and no smoothing (on my display).

Effect of disabling font smoothing in MacOS
Boldness comparison, this time with no smoothing
in MacOs

Anyway, we can’t put all the blame on web designers. Matching an extra-light
font with font-weight: 300 doesn’t seem to be a good idea, and matching it
with font-weight: 350 is just plain silly (and I’d need to use explicit
language to describe my feelings about Firefox using an extra-light font for
font-weight: 399).

Actually, we can put all the blame on them, as font-weight: 300 has always
(even in CSS Level 1) meant
“lighter than normal, even if the only lighter font is weight 100.” Firefox’s
behaviour of selecting an extra-light font for font-weight: 399 is in fact
conforming to the most recent draft specification.

MacOS’ somewhat bolder rendering of normal-weight fonts is therefore a
very weak excuse for using font-weight: 300, which literally forces the
browser to not use a normal-weight font (or bolder) unless there is no other
font available.

With that out of the way, let’s finally proceed to fix work around the
problem, since persuading thousands of web developers to fix their websites
doesn’t seem feasible at this point.

Linux, fontconfig, CSS

Font selection and appearance in Linux is
highly
configurable
via fontconfig. That is both a curse and a blessing. In this case, it is
quite advantageous.

There are a few handy command-line utilities which make it really easy to test
the configuration. I’ll use fc-list and fc-match here to see what
fonts I have and when DejaVu Sans ExtraLight is used:

$ fc-list | grep -F -w 'DejaVu Sans' | sort
/usr/share/fonts/truetype/dejavu/DejaVuSans-BoldOblique.ttf: DejaVu Sans:style=Bold Oblique
/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf: DejaVu Sans:style=Bold
/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed-BoldOblique.ttf: DejaVu Sans,DejaVu Sans Condensed:style=Condensed Bold Oblique,Bold Oblique
/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed-Bold.ttf: DejaVu Sans,DejaVu Sans Condensed:style=Condensed Bold,Bold
/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed-Oblique.ttf: DejaVu Sans,DejaVu Sans Condensed:style=Condensed Oblique,Oblique
/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed.ttf: DejaVu Sans,DejaVu Sans Condensed:style=Condensed,Book
/usr/share/fonts/truetype/dejavu/DejaVuSans-ExtraLight.ttf: DejaVu Sans,DejaVu Sans Light:style=ExtraLight
/usr/share/fonts/truetype/dejavu/DejaVuSansMono-BoldOblique.ttf: DejaVu Sans Mono:style=Bold Oblique
/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf: DejaVu Sans Mono:style=Bold
/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Oblique.ttf: DejaVu Sans Mono:style=Oblique
/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf: DejaVu Sans Mono:style=Book
/usr/share/fonts/truetype/dejavu/DejaVuSans-Oblique.ttf: DejaVu Sans:style=Oblique
/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf: DejaVu Sans:style=Book
/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf: DejaVu Sans:style=Bold
/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansMono-Bold.ttf: DejaVu Sans Mono:style=Bold
/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansMono.ttf: DejaVu Sans Mono:style=Book
/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf: DejaVu Sans:style=Book
$ fc-match -v sans 
  | grep -F -w -e style: -e weight: -e fullname:
        style: "Book"(s)
        fullname: "DejaVu Sans"(s)
        weight: 80(f)(s)

$ fc-match -v sans:weight=extralight 
  | grep -F -w -e style: -e weight: -e fullname:
        style: "ExtraLight"(s)
        fullname: "DejaVu Sans ExtraLight"(s)
        weight: 40(f)(s)

$ fc-match -v sans:weight=60 | grep -F -w -e weight: 
        weight: 40(f)(s)

$ fc-match -v sans:weight=61 | grep -F -w -e weight: 
        weight: 80(f)(s)

$ fc-match -v sans:weight=139 | grep -F -w -e weight: 
        weight: 80(f)(s)

$ fc-match -v sans:weight=140 | grep -F -w -e weight: 
        weight: 200(f)(s)

Fontconfig defines these symbolic font weights:

constant value
thin 0
extralight 40
ultralight 40
light 50
demilight 55
semilight 55
book 75
regular 80
normal 80
medium 100
demibold 180
semibold 180
bold 200
extrabold 205
black 210
heavy 210
Fontconfig weight constants

Apparently fontconfig selects the font with the closest weight requested.
That’s not what CSS needs, so browsers probably don’t use
fontconfig font patterns and therefore the usual fontconfig ways of avoiding
the extra-light font don’t
work.

But wait. Actually, some browsers do. The surf browser, built using
WebKitGTK, translates font-weigth: 300 to fontconfig weight 50,
font-weight: 200 to fontconfig weight 40 and font-weight: 100 to
fontconfig weight 0, which is a correct mapping, but it won’t result in
correct behaviour if only font weights 0 and 80 are available, as 80 is closer
to 60, but CSS mandates that 0 is chosen. (To find this out, I used
FC_DEBUG=1 surf.) Indeed, the fontconfig configuration suggested in the link
above is a sufficient workaround for the WebKitGTK browser:

surf before
$ FC_DEBUG=1 surf test.html |& grep -F -w -c ExtraLight
7
~/.config/fontconfig/fonts.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
	<match target="pattern">
		<test qual="any" name="family">
			<string>DejaVu Sans</string>
		</test>
		<test name="weight" compare="less">
			<const>book</const>
		</test>
		<edit name="weight" mode="assign" binding="same">
			<const>book</const>
		</edit>
	</match>
</fontconfig>
surf after
$ FC_DEBUG=1 surf test.html |& grep -F -w -c ExtraLight
0

In a real CSS-conforming browser, this won’t work as fontconfig is
presumably only used to list available fonts, and the font matching algorithm
then runs in the browser engine itself. One might also desperately attempt to
use fontconfig’s <match target="scan"> to lower
the weight of the font to 0 and hope the browser will select the nearer,
normal variant. Or at least I did desperately try that. That won’t work,
either:

  1. CSS still prefers a weight 0 font for font-weight: 300 when both weight 0
    and weight 400 are available.

  2. <match target="scan"> needs to be applied
    system-wide and fontconfig caches then need to be regenerated using
    fc-cache by root, as apparently the system-wide caches are preferred.
    Therefore it’s also impossible to apply this rule to a web browser only.

There is still one option left, fortunately: <selectfont>, which controls the set of available fonts. Its documentation
is quite high-level and in some aspects downright incorrect, but by reading
the source
we can conclude that it works like this:

  1. First, check if the filename is explicitly accepted by any <glob>. If it isn’t, then check whether it’s rejected,
    and only if it’s not accepted but it is explicitly rejected, skip the font.
    Otherwise continue.

    (The documentation claims that <glob> only
    filters directories, but this is fortunately not true.)

  2. Then, similarly, check if the font matches any accept <pattern> (these may test various font properties). If
    not, check reject patterns, and skip the font if rejected and not accepted.
    Otherwise continue and allow the font to be used.

  3. Order of configuration directives doesn’t matter, it’s just being added to
    glob/pattern accept/reject lists as the configuration is read.

The Solution

Fontconfig’s <selectfont> lets us hide DejaVu
Sans ExtraLight from the browser. If we want to keep the font available for
other applications (if we don’t, then it might be easier to just uninstall
it), let’s create a browser-specific fontconfig conf:

~/.config/fontconfig/browser.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
	<include>fonts.conf</include>

	<!-- disable DejaVu Sans ExtraLight, it tends to match font-weight: 300 -->
	<selectfont>
		<rejectfont>
			<glob>*/DejaVuSans-ExtraLight.ttf</glob>
		</rejectfont>
	</selectfont>
</fontconfig>

When we now set the FONTCONFIG_FILE=~/.config/fontconfig/browser.conf
environment variable, DejaVu Sans ExtraLight is nowhere to be seen:

$ FONTCONFIG_FILE=~/.config/fontconfig/browser.conf 
  fc-match -v sans:weight=40 | grep -F -w -e weight:
        weight: 80(f)(s)

$ FONTCONFIG_FILE=~/.config/fontconfig/browser.conf 
  fc-list | grep -F -w -c ExtraLight
0

Setting FONTCONFIG_FILE=~/.config/fontconfig/browser.conf for the browser is
launched is left as an exercise to the reader.

Appendix A: Why glob?

An observant reader might have noticed that the solution could be made more
robust by using <pattern> instead of <glob> and matching on the font weight, thus disabling all
light fonts. This is probably correct, but not usable in my case, as I already
use accept patterns to limit the available fonts to a few reasonable
ones
to prevent web designers from selecting hard to read font
faces. With the advent of web fonts, this workaround has become less effective
lately. 🙁




Source link

React Native debugger
Strategy

A Quick Guide For React Native Debugging


React Native Debugger is a powerful tool that helps developers debug React Native applications more quickly. It provides a suite of impressive features, such as UI inspector, redux inspector, breakpoints, and networking inspector. In this article, we are going to learn how to install and use the React Native Debugger, which will boost your development productivity by an order of magnitude.

In layman’s terms, a Debugger is a tool used for debugging. If you are a developer of any programming background, you must have already come across a debugger in your life. React Native Debugger is a standalone debugger tool built using the Electron framework. You can easily integrate this debugger tool along with the Chrome Dev tool. This debugger tool is based on the remote debugger which is included in React Native out of the box. However, the React Native debugger packs a lot more features than the remote one.

Installing React Native Debugger

The installation process is quite easy. We can simply download the latest installation file from the release page. For macOS, we can use Homebrew Cask to install. We can run the following command in the terminal:

After installation, simply open the React Native Debugger.app file in your Applications folder (or use Spotlight search to locate it).

Using The React Native Debugger

After a successful installation, we can simply open the React Native Debugger launch file. Hence, we will see the re-connection status on the title bar of the debugger app window. The debugging process will be executed on port 8081 as shown in the screenshot below:

React Native debugger

Now, when we run the React Native App on debug mode, we no longer need to open the browser. To have your React Native app enter debug mode, simply shake your device and choose “Debug” in the menu that pops up. If you are on a simulator, simply press Command + Shift + Z to open the menu.

Once the app is in the debug mode, with the React Native Debugger already opened, you will notice that the app connects to the React Native Debugger instead of the Chrome Dev Tools. All the console logs and debugging options will appear in the debugger app as shown in the screenshot below:

Chrome Dev tools debugger

Now, as we have learned about the installation and use cases, we are ready to use this Debugger app.

Chrome Dev Tools, React Dev Tools, Redux Dev tools

As you can notice, all these above important debugging extensions are under a single window. One benefit is that the app window size does not need to be full screen. Hence, we can use the debugger adjacently with the emulator as shown in the screenshot below:

Redux and react dev tools

The Network Inspector

When developing an app that needs to connect to a server, in general, we may not know what is going on behind the scenes. For example, when we are sending a request to the server, it may be difficult to track an error if it occurs. Developers usually debug this by printing or alerting error messages, but this is extremely time-consuming (despite the fact that most people do that), since it takes a lot of iterations to understand the entire state properly.

So, with the help of the React Native Debugger, we can easily monitor every inbound and outbound traffic request between app and server.

  • We can monitor React components, Redux store, also network activity all under a single window.
  • We can modify CORS on the fly.
  • We can modify the request header configurations such as names, origin, and user-agent. It enables us to control everything in a network request.

AsyncStorage Management

We can also debug AsyncStorage configurations using the React Native Debugger. We can simply log it to the console using the line in the code snippet below:

Once you add that line to your project, you will get the following result in the React Native Debugger console window:

React Native debugger

Debugging Redux State and Actions

As mentioned in the official React Native Debugger documentation, the tool already has an amazingly powerful built-in Redux Dev Tool. However, redux devtools will not work properly until we explicitly activate redux devtools in our app. In order to use redux devtools in the react-native-debugger, we need to activate redux in our app by adding some configuration lines to our  App.js file or to our redux main function as highlighted in the code snippet below:

Now if we reload our RN Debugger tool and run some actions, we will get the following result in our debugger console:

React Native debugger

As you can see, the React Native Debugger exposes the whole Redux state, so there’s no need for you to add breakpoints or console logs to understand how the data flows within the redux ecosystem. One amazing feature is that you can actually go back in time directly from the debugger, to reverse the Redux actions and re-run them again. Another huge feature is that you can visualize the precise diff between two state transitions, which is extremely helpful for apps with complex Redux states.

UI Inspector in React Native Debugger

The React Native Debugger tool also offers a powerful UI Inspector. We can use it to inspect app UI layouts that will help us tremendously in understanding the app’s UI structure. We can fix the UI elements style just by inspecting the layout.

React Native dev tools

This is by far the most efficient way to inspect the UI hierarchy in React Native apps and to debug layout issues.

Breakpoints in React Native Debugger

React Native Debugger also enables us to stop the execution task on a certain point. This will enable developers to understand app behavior or view some data states at any point in the app’s lifecycle. You can learn more details on adding and using breakpoints in our Debugging React Native app with Breakpoints article.

React Native debugger

Debugging Expo Apps

If you opted to use Expo CLI instead of React Native CLI for your app development, the good news is that the React Native Debugger works great with Expo too.

Usually, the Expo Debugger runs on port 19001. To switch to React Native Debugger for support, you need to run the following command:

Conclusion

In this article post, we discovered the React Native Debugger tool, its installation process, how it works, use-cases, and core features. Surely, it will make the life of any React Native developer easier, more productive, and more efficient. Your apps will also be more performant and will contain fewer bugs.

We highly recommend using the React Native Debugger instead of the Chrome Dev Tools, since it contains a set of core features that will boost your development speed tremendously.



Source link

r/webdev - Need Help in creating a table from desired values within existing table column
Strategy

Need Help in creating a table from desired values within exi…


Hello Everyone,
I am new with sql but trying to get at it.

I have a word press website and with the below table(my_table_1) in my SQL DB and form the data within the column3 of my_table_1 i want to create a table(wish_table) which i will be displaying in webpage with a php function ( i am not sure).

Here is my_table_1 :

r/webdev - Need Help in creating a table from desired values within existing table column

my_table_1

I want to make a new table with the text within column3 with the column headers as: your-name, your-email, customer-enquiry-subject, customer-enquiry-details and enquiry-customer-contact.

With my limited knowledge I have created a table_2 just with column3 from my_table_1 by using the following command:

CREATE TABLE table_2
    AS (SELECT form_value FROM my_table_1);

Created table_2 from above code:

r/webdev - Need Help in creating a table from desired values within existing table column

table_2

My question is how do i get the text values from individual cells of column 3 of my_table_1 such as s:16:”Abhishek Bacchan”, s:23:”This is enquiry subject”, s:24:”Bla Customer Details” and s:10:”1231231233″ and insert them to get my wish_table as :

r/webdev - Need Help in creating a table from desired values within existing table column

wish_table

Can someone please help me or suggest me if my approach to get the data within the column3 cell is wrong as if i need to use php to get the data instead of using SQL.

THANK YOU READER



Source link

Every Website is an Essay
Strategy

Every Website is an Essay


Every website that’s made me oooo and aaahhh lately has been of a special kind; they’re written and designed like essays. There’s an argument, a playfulness in the way that they’re not so much selling me something as they are trying to convince me of the thing. They use words and type and color in a way that makes me sit up and listen.

And I think that framing our work in this way lets us web designers explore exciting new possibilities. Instead of throwing a big carousel on the page and being done with it, thinking about making a website like an essay encourages us to focus on the tough questions. We need an introduction, we need to provide evidence for our statements, we need a conclusion, etc. This way we don’t have to get so caught up in the same old patterns that we’ve tried again and again in our work.

And by treating web design like an essay, we can be weird with the design. We can establish a distinct voice and make it sound like an honest-to-goodness human being wrote it, too.

One example of a website-as-an-essay is the Analogue Pocket site which uses real paragraphs to market their fancy new device.

Another example is the new email app Hey in which the website is nothing but paragraphs — no screenshots, no fancy product information. It’s almost feels like a political manifesto hammered onto a giant wooden door.

Apple’s marketing sites are little essays, too. Take this one section from the iPad Pro all about the LiDAR Scanner. It’s not so much trying to sell you an iPad at this point so much as it is trying to argue the case for LiDAR. And as with all good essays it answers the who, what, why, when, and how.

Another example is Stripe’s recent beautiful redesign. What I love more than the outrageously gorgeous animated gradients is the argument that the website is making. What is Stripe? How can I trust them? How easy is it to get set up? Who, what, why, when, how.

To be my own devil’s advocate for a bit though, we’re all familiar with this line of reasoning: Why care about the writing so much when people don’t read? Folks skim through a website. They don’t persevere with the text, they don’t engage with the writing, and you only have half a millisecond to hit them with something flashy before they leave. They can’t handle complex words or sentences. They can’t grasp complex ideas. So keep those paragraphs short! Remove all text from the page!

The implication here is that users are dumb. They can’t focus and they don’t care. You have to shout at them. And I kinda sorta hate that.

Instead, I think the opposite is true. They’ve seen the same boring websites for years. Everyone is tired of lifeless, humorless copywriting. They’ve seen all the animations, witnessed all the cool fonts, and in the face of all that stuff, they yawn. They yawn because it supports a bad argument, or more precisely, a bad essay; one that doesn’t charm the reader, or give them a reason to care.

So what if we made our websites more like essays and less like billboards that dot the freeways? What would that look like?



Source link