def getBeamStemCounters(self, y=None): u"""Calculate the stems and counters by a horizontal beam through the middle of the bounding box. This works best with the capital I. The value is uncached and should only be used if normal stem detection fails. Or in case of italic.""" beamStems = {} beamCounters = {} if y is None: y = (self.maxY - self.minY) / 2 line = ((-sys.maxsize, y), (sys.maxsize, y)) # Get intersections with this line. We can assume they are sorted set by x value intersections = self.intersectWithLine(line) # If could not make path or flattened path or no intersections or just one, give up. if intersections is None or len(intersections) < 2: return None # Now make Stem instance from these values, as if they we Verticals positions. # The difference is that we only have points, not point contexts here, # but for limited use that should not make a difference for entry in a Stem p0 = ap0 = None for n in range(0, len(intersections)): # Add this stem or counter to the result. Create point contexts, simulating vertical # We cannot just check on odd/even, as a line may pass exactly on the top/bottom of a curve. p = intersections[n] pUp = p[0], p[1] + 10 pDown = p[0], p[1] - 10 ap = APointContext((pUp, pUp, pUp, p, pDown, pDown, pDown)) # Simulate vertical context. if p0 is None: p0 = p ap0 = ap continue p1 = p0 ap1 = ap0 p0 = p ap0 = ap # If middle of the point is on black, then it is a stem. Otherwise it is a counter. mp = (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 if self.onBlack(mp): stem = self.STEM_CLASS(ap1, ap0, self.glyph.name) size = asInt(stem.size) # Make sure not to get floats as key if not size in beamStems: beamStems[size] = [] beamStems[size].append(stem) else: # Otherwise, middle point between intersections is on white, it must be a counter counter = self.COUNTER_CLASS(ap1, ap0, self.glyph.name) size = asInt(counter.size) # Make sure to get floats as key if not size in beamCounters: beamCounters[size] = [] beamCounters[size].append(counter) return beamStems, beamCounters
def findBars(self): """The @findBars@ method finds the bars in the current glyph and assigns them as dictionary to @self._bars@. Since we cannot use the CVT of the glyph (the analyzer is user to find these values, not to use them), we'll make an assumption about the pattern of vertices found. It is up to the calling function to make sure that the current glyph is relevant in the kind of vertices that we are looking for.""" horizontals = self.horizontals self._bars = bars = {} # BlueBars by separate property call self._roundBars = roundBars = {} self._straightRoundBars = straightRoundBars = {} self._verticalCounters = verticalCounters = {} self._verticalRoundCounters = verticalRoundCounters = {} self._verticalMixedCounters = verticalMixedCounters = {} self._allVerticalCounters = allVerticalCounters = {} # Space between all neighboring stems, running over white only. checked = set() # Store what we checked, to avoid doubles in the loops dimensions = self.dimensions if dimensions: for dimension in dimensions: # UFO only, needs to be written. if self.FLOQMEME_BAR in dimensions['types']: # Is this this dimension defining a stem? vbar = self.BAR_CLASS(dimensions['pc0'], dimensions['pc1'], self.glyph.name) size = vbar.size if not size in bars: bars[size] = [] bars[size].append(vbar) if not bars: # No "manual" hints defined, try to analyze from the found Vertical instances. for _, horizontal1 in sorted(horizontals.items()): # y1, horizontal1 for _, horizontal2 in sorted(horizontals.items()): # y2, horizontal2 if horizontal1 is horizontal2: continue # We need to loop through the points of the horizontal # separate, to find e.g. the vertical separate round bars # of the points of a ellipsis. Otherwise they will seen as one horizontal. for pc0 in horizontal1: for pc1 in horizontal2: # Skip if identical, they cannot be a stem. if pc0 is pc1: continue # Skip if we already examined this one. if (pc0.index, pc1.index) in checked: continue checked.add((pc0.index, pc1.index)) checked.add((pc1.index, pc0.index)) # Test if the y values are in range so this can be seen as stem pair # and test if this pair is spanning a black space and not covered in black. if self.isBar(pc0, pc1): # Add this bar to the result. hbar = self.BAR_CLASS(pc0, pc1, self.glyph.name) size = asInt(hbar.size) # Make sure not to get floats as keys if not size in bars: bars[size] = [] bars[size].append(hbar) elif self.isRoundBar(pc0, pc1): # If either of the point context is a curve extreme # then count this bar as round bar rbar = self.BAR_CLASS(pc0, pc1, self.glyph.name) size = asInt(rbar.size) # Make sure not to get floats as keys if not size in roundBars: roundBars[size] = [] roundBars[size].append(rbar) elif self.isStraightRoundBar(pc0, pc1): # If one side is straight and the other side is # round extreme then count this stem as # straight round bar. srbar = self.BAR_CLASS(pc0, pc1, self.glyph.name) size = asInt(srbar.size) # Make sure not to get floats as key if not size in straightRoundBars: straightRoundBars[size] = [] straightRoundBars[size].append(srbar) elif self.isVerticalCounter(pc0, pc1): # If there is just white space between the # points and they are some kind of extreme, # then assume this is a counter. counter = self.VERTICAL_COUNTER_CLASS(pc0, pc1, self.glyph.name) size = asInt(counter.size) if pc0.isHorizontalExtreme() and pc1.isHorizontalExtreme(): if not size in verticalCounters: verticalCounters[size] = [] verticalCounters[size].append(counter) elif pc0.isHorizontalRoundExtreme() and pc1.isHorizontalRoundExtreme(): if not size in verticalRoundCounters: verticalRoundCounters[size] = [] verticalRoundCounters[size].append(counter) else: if not size in verticalMixedCounters: verticalMixedCounters[size] = [] verticalMixedCounters[size].append(counter) if not size in allVerticalCounters: allVerticalCounters[size] = [] allVerticalCounters[size].append(counter) return self._bars
def findStems(self): """The @findStems@ method finds the stems in the current glyph and assigns them as dictionary to @self._stems@. Since we cannot use the CVT of the glyph (the analyzer is used to find these values, not to use them), we'll make an assumption about the pattern of vertices found. It is up to the calling function to make sure that the current glyph is relevant in the kind of vertices that we are looking for. NOTE: An alternative approach could be to make a Fourier analysis of all stem distances of the font, and so find out which are likely to have a stem distance. Additionally the stems are found by manual hints in the glyph, as generated by the Hint Editor. Since these stem definitions not necessarily run from point to point (instead an interpolated location on a curve or straight line can be used), a special kind of PointContext is added there. """ self._stems = stems = {} self._roundStems = roundStems = {} self._straightRoundStems = straightRoundStems = {} # Space between all neighboring stems, running over white only. self._allHorizontalCounters = horizontalCounters = {} verticals = self.verticals # Store what we checked, to avoid doubles in the loops. checked = set() for _, vertical1 in sorted(verticals.items()): # x1, vertical1 for _, vertical2 in sorted(verticals.items()): # x2, vertical2 if vertical1 is vertical2: continue # We need to loop through the points of the vertical # separate, to find e.g. the horizontal separate round stems # of the points of a column. Otherwise they will be seen as one vertical. for pc0 in vertical1: for pc1 in vertical2: # Skip if identical, they cannot be a stem. if pc0 is pc1: continue # Skip if we already examined this one. if (pc0.index, pc1.index) in checked: continue checked.add((pc0.index, pc1.index)) checked.add((pc1.index, pc0.index)) # Test if the y values are in range so this can be seen as stem pair # and test if this pair is spanning a black space and the lines are # not entirely covered in black. if self.isStem(pc0, pc1): # Add this stem to the result. stem = self.STEM_CLASS(pc0, pc1, self.glyph.name) size = asInt(stem.size) # Make sure not to get floats as key if not size in stems: stems[size] = [] stems[size].append(stem) elif self.isRoundStem(pc0, pc1): # If either of the point context is a curve extreme # then count this stem as round stem stem = self.STEM_CLASS(pc0, pc1, self.glyph.name) size = asInt(stem.size) # Make sure not to get floats as key if not size in roundStems: roundStems[size] = [] roundStems[size].append(stem) elif self.isStraightRoundStem(pc0, pc1): # If one side is straight and the other side is round extreme # then count this stem as straight round stem. stem = self.STEM_CLASS(pc0, pc1, self.glyph.name) size = asInt(stem.size) # Make sure not to get floats as key if not size in straightRoundStems: straightRoundStems[size] = [] straightRoundStems[size].append(stem) elif self.isHorizontalCounter(pc0, pc1): # If there is just whitspace between the points and they are some kind of extreme, # then assume this is a counter. counter = self.COUNTER_CLASS(pc0, pc1, self.glyph.name) size = asInt(counter.size) if not size in horizontalCounters: horizontalCounters[size] = [] horizontalCounters[size].append(counter) return self._stems