           draw_dashed_border(local_pos, distance_from_mix_line);
           break;
         case BORDER_STYLE_DOTTED:
+            draw_dotted_border(local_pos, distance_from_mix_line);
+            break;
         case BORDER_STYLE_OUTSET:
         case BORDER_STYLE_INSET:
         case BORDER_STYLE_SOLID:

    }
    case PST_BOTTOM:
    case PST_TOP: {
      vec2 piece_relative_position = vLocalPos - vPieceRect.xy;
      oFragColor = draw_dotted_edge(piece_relative_position.yx, vPieceRect.wz, pixels_per_fragment);
      break;
    }
    case PST_LEFT:
    case PST_RIGHT:
    {
      vec2 piece_relative_position = vLocalPos - vPieceRect.xy;
      oFragColor = draw_dotted_edge(piece_relative_position.xy, vPieceRect.zw, pixels_per_fragment);
      break;
    }
  }
}

#endif

vec4 draw_dashed_edge(float position, float border_width, float pixels_per_fragment) {
  // TODO: Investigate exactly what FF does.

  return alpha_for_solid_border(distance_from_ref, inner_radius, outer_radius, pixels_per_fragment);
}

vec4 draw_dotted_edge(vec2 local_pos, vec4 piece_rect, float pixels_per_fragment) {
  // We don't use pixels_per_fragment here, since it can change along the edge
  // of a transformed border edge. We want this calculation to be consistent
  // across the entire edge so that the positioning of the dots stays the same.
  float two_pixels = 2 * length(fwidth(vLocalPos.xy));

  // Circle diameter is stroke width, minus a couple pixels to account for anti-aliasing.
  float circle_diameter = max(piece_rect.z - two_pixels, min(piece_rect.z, two_pixels));

  // We want to spread the circles across the edge, but keep one circle diameter at the end
  // reserved for two half-circles which connect to the corners.
  float edge_available = piece_rect.w - (circle_diameter * 2);
  float number_of_circles = floor(edge_available / (circle_diameter * 2));

  // Here we are initializing the distance from the y coordinate of the center of the circle to
  // the closest end half-circle.
  vec2 relative_pos = local_pos - piece_rect.xy;
  float y_distance = min(relative_pos.y, piece_rect.w - relative_pos.y);

  if (number_of_circles > 0) {
    // Spread the circles throughout the edge, to distribute the extra space evenly. We want
    // to ensure that we have at last two pixels of space for each circle so that they aren't
    // touching.
    float space_for_each_circle = ceil(max(edge_available / number_of_circles, two_pixels));

    float first_half_circle_space = circle_diameter;

    float circle_index = (relative_pos.y - first_half_circle_space) / space_for_each_circle;
    circle_index = floor(clamp(circle_index, 0, number_of_circles - 1));

    float circle_y_pos =
      circle_index * space_for_each_circle + (space_for_each_circle / 2) + circle_diameter;
    y_distance = min(abs(circle_y_pos - relative_pos.y), y_distance);
  }

  float distance_from_circle_center = length(vec2(relative_pos.x - (piece_rect.z / 2), y_distance));
  float distance_from_circle_edge = distance_from_circle_center - (circle_diameter / 2);

  // Don't anti-alias if the circle diameter is small to avoid a blur of color.
  if (circle_diameter < two_pixels && distance_from_circle_edge > 0)
