diff --git a/Lib/ufo2ft/filters/scaleUPM.py b/Lib/ufo2ft/filters/scaleUPM.py
new file mode 100644
index 000000000..4d72f7959
--- /dev/null
+++ b/Lib/ufo2ft/filters/scaleUPM.py
@@ -0,0 +1,146 @@
+from ufo2ft.filters import BaseFilter
+
+
+class ScaleUPMFilter(BaseFilter):
+
+ """ This filter scales the font to a new upm value. Set the target upm in
+ an UFO like this:
+ com.github.googlei18n.ufo2ft.filters
+
+
+ name
+ scaleUPM
+ kwargs
+
+ unitsPerEm
+ 2048
+
+
+
+ """
+
+ _kwargs = {
+ "unitsPerEm": 1000,
+ }
+
+ def _scaleGlyph(self, glyph):
+ """
+ Scale a glyph
+ """
+ for contour in glyph:
+ for point in contour.points:
+ point.x *= self.factor
+ point.y *= self.factor
+
+ for anchor in glyph.anchors:
+ anchor.x *= self.factor
+ anchor.y *= self.factor
+
+ glyph.width *= self.factor
+
+ def _scaleList(self, obj, name):
+ """
+ Scale a font info property that is a list, i.e. scale each value.
+ """
+ lst = getattr(obj, name)
+ if lst is None:
+ return
+
+ lst = [self.factor * v for v in lst]
+ setattr(obj, name, lst)
+
+ def _scaleProperty(self, obj, name):
+ prop = getattr(obj, name)
+ if prop is None:
+ return
+
+ setattr(obj, name, self.factor * prop)
+
+ def __call__(self, font, glyphSet=None):
+ newUnitsPerEm = int(self.options.unitsPerEm)
+ if font.info.unitsPerEm == newUnitsPerEm:
+ return False
+
+ self.factor = newUnitsPerEm / font.info.unitsPerEm
+
+ # Scale glyphs
+ super(ScaleUPMFilter, self).__call__(font, glyphSet)
+
+ # Scale kerning
+ for pair, value in font.kerning.items():
+ font.kerning[pair] = value * self.factor
+
+ # TODO: Change positioning feature code
+
+ # Scale info values
+ for prop in (
+ "descender",
+ "xHeight",
+ "capHeight",
+ "ascender",
+ "openTypeHheaAscender",
+ "openTypeHheaDescender",
+ "openTypeHheaLineGap",
+ "openTypeHheaCaretOffset",
+ "openTypeOS2TypoAscender",
+ "openTypeOS2TypoDescender",
+ "openTypeOS2TypoLineGap",
+ "openTypeOS2WinAscent",
+ "openTypeOS2WinDescent",
+ "openTypeOS2SubscriptXSize",
+ "openTypeOS2SubscriptYSize",
+ "openTypeOS2SubscriptXOffset",
+ "openTypeOS2SubscriptYOffset",
+ "openTypeOS2SuperscriptXSize",
+ "openTypeOS2SuperscriptYSize",
+ "openTypeOS2SuperscriptXOffset",
+ "openTypeOS2SuperscriptYOffset",
+ "openTypeOS2StrikeoutSize",
+ "openTypeOS2StrikeoutPosition",
+ "openTypeVheaVertTypoAscender",
+ "openTypeVheaVertTypoDescender",
+ "openTypeVheaVertTypoLineGap",
+ "openTypeVheaCaretOffset",
+ "postscriptUnderlineThickness",
+ "postscriptUnderlinePosition",
+ ):
+ self._scaleProperty(font.info, prop)
+
+ for prop in (
+ "postscriptBlueValues",
+ "postscriptOtherBlues",
+ "postscriptFamilyOtherBlues",
+ "postscriptStemSnapH",
+ "postscriptStemSnapV",
+ "postscriptBlueFuzz",
+ "postscriptBlueShift",
+ "postscriptBlueScale",
+ "postscriptDefaultWidthX",
+ "postscriptNominalWidthX",
+ ):
+ self._scaleList(font.info, prop)
+
+ # Finally set new UPM
+ font.info.unitsPerEm = newUnitsPerEm
+
+ return True
+
+ def filter(self, glyph):
+ if getattr(self.context, "skipCurrentFont", False):
+ return False
+
+ # Scale glyph
+ self._scaleGlyph(glyph)
+
+ # scale component offsets
+ for i in range(len(glyph.components)):
+ comp = glyph.components[i]
+ xS, xyS, yxS, yS, xOff, yOff = comp.transformation
+ comp.transformation = (
+ xS,
+ xyS,
+ yxS,
+ yS,
+ xOff * self.factor,
+ yOff * self.factor,
+ )
diff --git a/tests/data/UnitsPerEmTest.ufo/features.fea b/tests/data/UnitsPerEmTest.ufo/features.fea
new file mode 100644
index 000000000..3d953b3c6
--- /dev/null
+++ b/tests/data/UnitsPerEmTest.ufo/features.fea
@@ -0,0 +1,7 @@
+# automatic
+@Uppercase = [ A O ];
+
+feature cpsp {
+pos @Uppercase <10 0 20 0>;
+} cpsp;
+
diff --git a/tests/data/UnitsPerEmTest.ufo/fontinfo.plist b/tests/data/UnitsPerEmTest.ufo/fontinfo.plist
new file mode 100644
index 000000000..859fac1c3
--- /dev/null
+++ b/tests/data/UnitsPerEmTest.ufo/fontinfo.plist
@@ -0,0 +1,64 @@
+
+
+
+
+ ascender
+ 800
+ capHeight
+ 720
+ descender
+ -160
+ openTypeHeadCreated
+ 2020/12/01 11:22:55
+ openTypeHheaAscender
+ 800
+ openTypeHheaDescender
+ -160
+ openTypeHheaLineGap
+ 240
+ openTypeOS2TypoAscender
+ 800
+ openTypeOS2TypoDescender
+ -160
+ openTypeOS2TypoLineGap
+ 240
+ openTypeOS2WinAscent
+ 800
+ openTypeOS2WinDescent
+ 200
+ postscriptBlueValues
+
+ -14.0
+ 0.0
+ 560.0
+ 574.0
+ 720.0
+ 734.0
+ 800.0
+ 814.0
+
+ postscriptOtherBlues
+
+ -176.0
+ -160.0
+
+ postscriptStemSnapH
+
+ 70.0
+
+ postscriptStemSnapV
+
+ 80.0
+
+ styleName
+ Regular
+ unitsPerEm
+ 1000
+ versionMajor
+ 2
+ versionMinor
+ 0
+ xHeight
+ 560
+
+
diff --git a/tests/data/UnitsPerEmTest.ufo/glyphs.public.background/A_.glif b/tests/data/UnitsPerEmTest.ufo/glyphs.public.background/A_.glif
new file mode 100644
index 000000000..54dc77da6
--- /dev/null
+++ b/tests/data/UnitsPerEmTest.ufo/glyphs.public.background/A_.glif
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/UnitsPerEmTest.ufo/glyphs.public.background/contents.plist b/tests/data/UnitsPerEmTest.ufo/glyphs.public.background/contents.plist
new file mode 100644
index 000000000..43097d5cd
--- /dev/null
+++ b/tests/data/UnitsPerEmTest.ufo/glyphs.public.background/contents.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ A
+ A_.glif
+
+
diff --git a/tests/data/UnitsPerEmTest.ufo/glyphs/A_.glif b/tests/data/UnitsPerEmTest.ufo/glyphs/A_.glif
new file mode 100644
index 000000000..6b8bc01e8
--- /dev/null
+++ b/tests/data/UnitsPerEmTest.ufo/glyphs/A_.glif
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/UnitsPerEmTest.ufo/glyphs/O_.glif b/tests/data/UnitsPerEmTest.ufo/glyphs/O_.glif
new file mode 100644
index 000000000..4484e17ed
--- /dev/null
+++ b/tests/data/UnitsPerEmTest.ufo/glyphs/O_.glif
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/UnitsPerEmTest.ufo/glyphs/contents.plist b/tests/data/UnitsPerEmTest.ufo/glyphs/contents.plist
new file mode 100644
index 000000000..84eb2372d
--- /dev/null
+++ b/tests/data/UnitsPerEmTest.ufo/glyphs/contents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ A
+ A_.glif
+ O
+ O_.glif
+
+
diff --git a/tests/data/UnitsPerEmTest.ufo/kerning.plist b/tests/data/UnitsPerEmTest.ufo/kerning.plist
new file mode 100644
index 000000000..810fe4447
--- /dev/null
+++ b/tests/data/UnitsPerEmTest.ufo/kerning.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ A
+
+ O
+ -25
+
+ O
+
+ A
+ -25
+
+
+
diff --git a/tests/data/UnitsPerEmTest.ufo/layercontents.plist b/tests/data/UnitsPerEmTest.ufo/layercontents.plist
new file mode 100644
index 000000000..b1e911588
--- /dev/null
+++ b/tests/data/UnitsPerEmTest.ufo/layercontents.plist
@@ -0,0 +1,14 @@
+
+
+
+
+
+ public.default
+ glyphs
+
+
+ public.background
+ glyphs.public.background
+
+
+
diff --git a/tests/data/UnitsPerEmTest.ufo/lib.plist b/tests/data/UnitsPerEmTest.ufo/lib.plist
new file mode 100644
index 000000000..3e9ac461b
--- /dev/null
+++ b/tests/data/UnitsPerEmTest.ufo/lib.plist
@@ -0,0 +1,46 @@
+
+
+
+
+ com.github.googlei18n.ufo2ft.filters
+
+
+ name
+ scaleUPM
+ kwargs
+
+ unitsPerEm
+ 2048
+
+
+
+ com.schriftgestaltung.disablesAutomaticAlignment
+
+ com.schriftgestaltung.fontMaster.customParameters
+
+
+ name
+ Default Layer Width
+ value
+ 560
+
+
+ com.schriftgestaltung.fontMasterID
+ 83692D61-586C-413E-9B5A-1AF652750951
+ com.schriftgestaltung.glyphOrder
+
+ com.schriftgestaltung.gridSize
+ 80
+ com.schriftgestaltung.gridSubDivision
+ 80
+ com.schriftgestaltung.useNiceNames
+
+ com.schriftgestaltung.weightValue
+ 100
+ public.glyphOrder
+
+ A
+ O
+
+
+
diff --git a/tests/data/UnitsPerEmTest.ufo/metainfo.plist b/tests/data/UnitsPerEmTest.ufo/metainfo.plist
new file mode 100644
index 000000000..74e4b3b4f
--- /dev/null
+++ b/tests/data/UnitsPerEmTest.ufo/metainfo.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ creator
+ com.schriftgestaltung.GlyphsUFOExport
+ formatVersion
+ 3
+
+